/*
 *
 */
package com.ePadink;


import java.awt.Image;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class BMPWriter
{
  protected static final boolean STATUS_MESSAGES = false;
  protected static final int ROWS_GRABBED_AT_A_TIME = 75;
  protected static final int BI_RGB = 0;
  protected static final int BI_COMPRESSED_RLE8 = 1;
  protected static final int BI_COMPRESSED_RLE4 = 2;
  protected static DataOutputStream out;
  protected static ByteArrayOutputStream bos = new ByteArrayOutputStream();
  protected static String fileName = "None";

  public static byte[] getBMPBytes()
  {
    try
    {
      byte[] temp = null;
      temp = bos.toByteArray();
      bos.reset();
      return temp; } catch (Exception ex) {
    }
    return null;
  }

  public static synchronized void write(Image image)
  {
    int width = image.getWidth(null);
    int height = image.getHeight(null);
    long lastStatusTime = 0L;

    int[] pixels = new int[height * width];
    byte[] indexedPixels = new byte[height * width];
    int[] colourMap = new int[256];
    int numColours = 0;

    for (int y = 0; y < height; y += 75)
    {
      if (System.currentTimeMillis() - lastStatusTime > 1000L)
      {
        lastStatusTime = System.currentTimeMillis();
      }

      PixelGrabber grabber = new PixelGrabber(image, 0, y, width, Math.min(75, height - y), pixels, y * width, width);
      try
      {
        if (!grabber.grabPixels())
        {
          System.out.println("BMPWriter Failed: Unable to get picture image");

          return;
        }
      }
      catch (InterruptedException e)
      {
        System.out.println("BMPWriter Failed: Unable to get picture image");

        return;
      }

    }

    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int match = 0;
        int pixel = pixels[(y * width + x)] & 0xFFFFFF;
        for (match = 0; (match < numColours) && 
          (pixel != colourMap[match]); match++);
        if (match > 255)
        {
          indexedPixels = null;

          write24BitBMP(pixels, width, height);
          return;
        }

        indexedPixels[(y * width + x)] = ((byte)match);

        if (match == numColours)
        {
          colourMap[(numColours++)] = pixel;
        }
      }

    }

    pixels = null;
    if (numColours <= 2)
    {
      write1BitBMP(indexedPixels, colourMap, width, height);
    }
    else if (numColours <= 16)
    {
      write4BitBMP(indexedPixels, colourMap, width, height);
    }
    else
    {
      write8BitBMP(indexedPixels, colourMap, width, height);
    }
  }

  protected static void write1BitBMP(byte[] pixels, int[] colourMap, int width, int height)
  {
    long lastStatusTime = 0L;

    int padding = (4 - (width + 7) / 8 % 4) % 4;
    int imageSize = ((width + 7) / 8 + padding) * height;
    byte[] picture = new byte[imageSize];

    int ptr = 0;
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int pixel = pixels[((height - 1 - y) * width + x)];
        int tmp78_76 = ptr;
        byte[] tmp78_74 = picture; tmp78_74[tmp78_76] = ((byte)(tmp78_74[tmp78_76] | pixel << 7 - x % 8));
        if ((x % 8 == 7) || (x == width - 1)) {
          ptr++;
        }
      }

      for (int cnt = 0; cnt < padding; cnt++)
      {
        picture[(ptr++)] = 0;
      }

    }

    writeBMPFile(imageSize, 2, width, height, 1, 0, colourMap, picture);
  }

  protected static synchronized void write24BitBMP(int[] pixels, int width, int height)
  {
    long lastStatusTime = 0L;

    int padding = (4 - width * 3 % 4) % 4;
    int imageSize = (width * 3 + padding) * height;
    byte[] picture = new byte[imageSize];

    int ptr = 0;
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int pixel = pixels[((height - 1 - y) * width + x)];
        picture[(ptr++)] = ((byte)(pixel & 0xFF));
        picture[(ptr++)] = ((byte)(pixel >> 8 & 0xFF));
        picture[(ptr++)] = ((byte)(pixel >> 16 & 0xFF));
      }

      for (int cnt = 0; cnt < padding; cnt++)
      {
        picture[(ptr++)] = 0;
      }

    }

    writeBMPFile(imageSize, 0, width, height, 24, 0, null, picture);
  }

  protected static void write4BitBMP(byte[] pixels, int[] colourMap, int width, int height)
  {
    int[] bitShift = { 4, 0 };
    long lastStatusTime = 0L;

    int padding = (4 - (width + 1) / 2 % 4) % 4;
    int imageSize = ((width + 1) / 2 + padding) * height;
    byte[] picture = new byte[imageSize];

    int ptr = 0;
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int pixel = pixels[((height - 1 - y) * width + x)];
        int tmp87_85 = ptr;
        byte[] tmp87_83 = picture; tmp87_83[tmp87_85] = ((byte)(tmp87_83[tmp87_85] | pixel << bitShift[(x % 2)]));
        if ((x % 2 == 1) || (x == width - 1)) {
          ptr++;
        }
      }

      for (int cnt = 0; cnt < padding; cnt++)
      {
        picture[(ptr++)] = 0;
      }

    }

    byte[] compressed = new byte[imageSize];
    int byteWidth = width / 2 + padding;
    int compressedPtr = 0;
    for (int y = 0; y < height; y++)
    {
      int x = 0;
      while (x < (width + 1) / 2)
      {
        int startX = x;

        boolean foundIdenticals = false;
        while ((!foundIdenticals) && (x < width - 4))
        {
          int loc = y * byteWidth + x;
          if ((picture[loc] == picture[(loc + 1)]) && (picture[(loc + 1)] == picture[(loc + 2)]))
          {
            foundIdenticals = true;
            break;
          }
          x++;
        }

        if (!foundIdenticals) {
          x = (width + 1) / 2;
        }

        while (x - startX > 2)
        {
          if (compressedPtr + 300 > imageSize)
          {
            writeBMPFile(imageSize, 16, width, height, 4, 0, colourMap, picture);

            return;
          }

          int size = Math.min(x - startX, 127);
          compressed[(compressedPtr++)] = 0;
          compressed[(compressedPtr++)] = ((byte)(size * 2));
          int picPtr = y * byteWidth + startX;
          for (int cnt = 0; cnt < size; cnt++)
          {
            compressed[(compressedPtr++)] = picture[(picPtr + cnt)];
          }
          if (size % 2 == 1)
            compressed[(compressedPtr++)] = 0;
          startX += size;
        }
        x = startX;

        if (x == width)
        {
          break;
        }
        byte pixel = picture[(y * byteWidth + x)];
        boolean identical = true;
        x++;
        while ((identical) && (x < (width + 1) / 2))
        {
          if (picture[(y * byteWidth + x)] != pixel)
          {
            identical = false;
            break;
          }
          x++;
        }

        while (x != startX)
        {
          if (compressedPtr + 300 > imageSize)
          {
            writeBMPFile(imageSize, 16, width, height, 4, 0, colourMap, picture);

            return;
          }

          int size = Math.min(x - startX, 127);
          compressed[(compressedPtr++)] = ((byte)(size * 2));
          compressed[(compressedPtr++)] = pixel;
          startX += size;
        }

      }

      compressed[(compressedPtr++)] = 0;
      compressed[(compressedPtr++)] = 0;
    }

    compressed[(compressedPtr++)] = 0;
    compressed[(compressedPtr++)] = 1;

    writeBMPFile(compressedPtr, 16, width, height, 4, 2, colourMap, compressed);
  }

  protected static void write8BitBMP(byte[] pixels, int[] colourMap, int width, int height)
  {
    long lastStatusTime = 0L;

    int padding = (4 - width % 4) % 4;
    int imageSize = (width + padding) * height;
    byte[] picture = new byte[imageSize];

    int ptr = 0;
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        int tmp54_51 = (ptr++);
        byte[] tmp54_47 = picture; tmp54_47[tmp54_51] = ((byte)(tmp54_47[tmp54_51] | pixels[((height - 1 - y) * width + x)]));
      }

      for (int cnt = 0; cnt < padding; cnt++)
      {
        picture[(ptr++)] = 0;
      }

    }

    byte[] compressed = new byte[imageSize];
    int byteWidth = width + padding;
    int compressedPtr = 0;
    for (int y = 0; y < height; y++)
    {
      int x = 0;
      while (x < width)
      {
        int startX = x;

        boolean foundIdenticals = false;
        while ((!foundIdenticals) && (x < width - 4))
        {
          int loc = y * byteWidth + x;
          if ((picture[loc] == picture[(loc + 1)]) && (picture[(loc + 1)] == picture[(loc + 2)]))
          {
            foundIdenticals = true;
            break;
          }
          x++;
        }

        if (!foundIdenticals) {
          x = width;
        }

        while (x - startX > 2)
        {
          if (compressedPtr + 300 > imageSize)
          {
            writeBMPFile(imageSize, 256, width, height, 8, 0, colourMap, picture);

            return;
          }

          int size = Math.min(x - startX, 255);
          compressed[(compressedPtr++)] = 0;
          compressed[(compressedPtr++)] = ((byte)size);
          int picPtr = y * byteWidth + startX;
          for (int cnt = 0; cnt < size; cnt++)
          {
            compressed[(compressedPtr++)] = picture[(picPtr + cnt)];
          }
          if (size % 2 == 1)
            compressed[(compressedPtr++)] = 0;
          startX += size;
        }
        x = startX;

        if (x == width)
        {
          break;
        }
        byte pixel = picture[(y * byteWidth + x)];
        boolean identical = true;
        x++;
        while ((identical) && (x < width))
        {
          if (picture[(y * byteWidth + x)] != pixel)
          {
            identical = false;
            break;
          }
          x++;
        }

        while (x != startX)
        {
          if (compressedPtr + 300 > imageSize)
          {
            writeBMPFile(imageSize, 256, width, height, 8, 0, colourMap, picture);

            return;
          }

          int size = Math.min(x - startX, 255);
          compressed[(compressedPtr++)] = ((byte)size);
          compressed[(compressedPtr++)] = pixel;
          startX += size;
        }

      }

      compressed[(compressedPtr++)] = 0;
      compressed[(compressedPtr++)] = 0;
    }

    compressed[(compressedPtr++)] = 0;
    compressed[(compressedPtr++)] = 1;

    writeBMPFile(compressedPtr, 256, width, height, 8, 1, colourMap, compressed);
  }

  protected static void writeBMPFile(int imageSize, int paletteEntries, int width, int height, int bitDepth, int compressionType, int[] colourMap, byte[] buffer)
  {
    try
    {
      out = new DataOutputStream(bos);
    }
    catch (Exception e)
    {
      System.out.println("BMPWriter Failed");

      return;
    }

    try
    {
      writeShort(19778);
      writeInt(imageSize + 54 + paletteEntries * 4);
      writeShort(0);
      writeShort(0);
      writeInt(54 + paletteEntries * 4);

      writeInt(40);
      writeInt(width);
      writeInt(height);
      writeShort(1);
      writeShort(bitDepth);
      writeInt(compressionType);
      writeInt(imageSize);
      writeInt(2835);
      writeInt(2835);
      writeInt(paletteEntries);
      writeInt(paletteEntries);

      for (int colours = 0; colours < paletteEntries; colours++)
      {
        int colour = colourMap[colours];

        out.writeByte(colour & 0xFF);
        out.writeByte(colour >> 8 & 0xFF);
        out.writeByte(colour >> 16 & 0xFF);
        out.writeByte(0);
      }

      out.write(buffer, 0, imageSize);

      out.close();

      out = null;
    }
    catch (IOException e)
    {
      System.out.println("BMPWriter Failed: An error occurred whilewriting to \"" + fileName + "\"");
    }
    finally
    {
      if (out != null)
      {
        try
        {
          out.close();
          out = null;
        }
        catch (IOException e)
        {
        }
      }
    }
  }

  protected static void writeInt(int number)
    throws IOException
  {
    out.writeByte(number & 0xFF);
    out.writeByte(number >> 8 & 0xFF);
    out.writeByte(number >> 16 & 0xFF);
    out.writeByte(number >> 24 & 0xFF);
  }

  protected static void writeShort(int number)
    throws IOException
  {
    out.writeByte(number & 0xFF);
    out.writeByte(number >> 8 & 0xFF);
  }
}